/*
 * Copyright © 2007 Neaveru Freedland
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of
 * Novell, Inc. not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 * Novell, Inc. makes no representations about the suitability of this
 * software for any purpose. It is provided "as is" without express or
 * implied warranty.
 *
 * Author: Neaveru Freedland <neaveru@gmail.com>
 */

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/Xproto.h>
#include <X11/cursorfont.h>

#include <X11/extensions/shape.h>
#include <X11/extensions/Xrender.h>

#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>

#ifndef MAX
#define MAX(a,b) (((a)>(b))?(a):(b))
#endif

#ifndef MIN
#define MIN(a,b) (((a)<(b))?(a):(b))
#endif

#define MAX_ARGS 256

#define SOLID 0xffffffff

#define NAME "bwpropset"

/* arg, atom name, help message */
static char *wmStateArgs[][3] = {
	{"s",	"_NET_WM_STATE_STICKY", "Set Sticky"},
	{"st",	"_NET_WM_STATE_SKIP_TASKBAR", "Skip taskbar"},
	{"sp",	"_NET_WM_STATE_SKIP_PAGER", "Skip pager"},
	{"a",	"_NET_WM_STATE_ABOVE", "Set Above"},
	{"b",	"_NET_WM_STATE_BELOW", "Set Below"}
};
static int nWmStateArgs = sizeof(wmStateArgs) / sizeof(wmStateArgs[0]);

/* arg, atom name, help message */
static char *wmLevelArgs[][3] = {
	{"op", "_NET_WM_WINDOW_OPACITY", "Set Opacity"},
	{"br", "_NET_WM_WINDOW_BRIGHTNESS", "Set Brightness"},
	{"sa", "_NET_WM_WINDOW_SATURATION", "Set Saturation"}
};
static int nWmLevelArgs = sizeof(wmLevelArgs) / sizeof(wmLevelArgs[0]);

struct levelData
{
	char *atom_name;
	float percentage; /* between 0 and 1 */
};

static int quiet = 0;

static void
fatalError(char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	vfprintf(stderr, format, ap);
	va_end(ap);
	exit(1);
}

//Print some usage/help information
static void 
usage(void)
{
	int i;
	char *stateFormat = "\t-%-3s\t%s\n";
	fprintf(stderr, "Usage: %s <option> [options]\n", NAME);

	fprintf(stderr, "\nState Options:\n");
	for (i = 0; i < nWmStateArgs; i++)
	{
		fprintf(stderr,
			stateFormat, wmStateArgs[i][0], wmStateArgs[i][2]);
	}
	fprintf(stderr, stateFormat, "fs", "Set fullscreen (ignores 'allowed actions')\n");
	fprintf(stderr, stateFormat, "nb", "Remove Border (experimental)\n");
	fprintf(stderr, stateFormat, "nf", "No Focus\n");
	fprintf(stderr, stateFormat, "ni", "No Input\n");

	fprintf(stderr, "\nDisplay Level Options (must be followed by argument between 0 and 1):\n");
	for (i = 0; i < nWmLevelArgs; i++)
	{
		fprintf(stderr, "\t-%s [0,1]\t%s\n",
			wmLevelArgs[i][0], wmLevelArgs[i][2]);
	}

	fprintf(stderr, "\nMisc Options:\n");
	fprintf(stderr, "\t-g [<width>x<height>][+<xoffset>+<yoffset>]\tSet Window Dimensions (ignores wm hints)\n");
	fprintf(stderr, "\t-d host:dpy\tX server to contact\n");
	fprintf(stderr, "\t-id\tWindow id\n");
	fprintf(stderr, "\t-q\tQuiet Mode\n");
	exit(1);
}

static void bwpsPrintf(char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	if (!quiet)
		vprintf(format, ap);
	va_end(ap);
}

//stolen, select window with a cursor
static Window 
selectWindow(Display *dpy, unsigned int screen)
{
	int status;
	Cursor cursor;
	XEvent event;
	Window target_win = None, root = RootWindow(dpy,screen);
	int buttons = 0;

	/* Make the target cursor */
	cursor = XCreateFontCursor(dpy, XC_crosshair);

	/* Grab the pointer using target cursor, letting it room all over */
	status = XGrabPointer(dpy, root, False,
		ButtonPressMask|ButtonReleaseMask, GrabModeSync,
		GrabModeAsync, root, cursor, CurrentTime);
	if (status != GrabSuccess) fatalError("Can't grab the mouse.");

	/* Let the user select a window... */
	while ((target_win == None) || (buttons != 0)) {
	    /* allow one more event */
	    XAllowEvents(dpy, SyncPointer, CurrentTime);
	    XWindowEvent(dpy, root, ButtonPressMask|ButtonReleaseMask, &event);
	    switch (event.type) {
	    case ButtonPress:
	    	if (target_win == None) {
				target_win = event.xbutton.subwindow; /* window selected */
				if (target_win == None) target_win = root;
	        }
	        buttons++;
	        break;
	    case ButtonRelease:
	        if (buttons > 0) /* there may have been some down before we started */
	    		buttons--;
	        break;
	    }
	} 

	XUngrabPointer(dpy, CurrentTime);      /* Done with pointer */

	return(target_win);
}

static void
bwpsUnmapWindow(Display *dpy, Window win)
{
	XWindowAttributes attribs;

	XSelectInput(dpy, win, StructureNotifyMask);
	XGetWindowAttributes(dpy, win, &attribs);

	if (attribs.map_state != IsUnmapped)
	{
		XUnmapWindow(dpy, win);
		while (1)
		{
			XEvent e;
			XNextEvent(dpy, &e);
			if (e.type == UnmapNotify && e.xunmap.window == win)
				break;
		}
	}

	XSelectInput(dpy, win, NoEventMask);
}

static void
setNoBorder(Display *dpy, Window win, int noBorder)
{
	//this is iffy
	//FIXME, i KNOW there's more to do with this
	Atom prop = XInternAtom(dpy, "_MOTIF_WM_HINTS", 0);
	long data[] = {1 << 1, 0, !noBorder};
	XChangeProperty (dpy,
					 win,
					 prop,
					 prop,
					 8,
					 PropModeReplace,
					 (unsigned char *) data,
					 sizeof(long) * 3);
}

/* Allows the fullscreen action */
static void
initializeFullscreen(Display *dpy, Window win)
{
	const int maxActionsAllowed = 256;
	char dummy[32];
	unsigned long nActionsReturned;
	Atom aAllowedActions = XInternAtom(dpy, "_NET_WM_ALLOWED_ACTIONS", 0);
	int nActions, nSendActions;
	Atom *actionsReturned;
	Atom *sendActions;
	int i;

	Atom actions[] = { 
		XInternAtom(dpy, "_NET_WM_ACTION_MAXIMIZE_HORZ", 0),
		XInternAtom(dpy, "_NET_WM_ACTION_MAXIMIZE_VERT", 0),
		XInternAtom(dpy, "_NET_WM_ACTION_FULLSCREEN", 0)};
		//XInternAtom(dpy, "_NET_WM_ACTION_RESIZE", 0)};
	nActions = sizeof(actions) / sizeof(actions[0]);

	XGetWindowProperty(dpy, win, aAllowedActions,
		0, maxActionsAllowed, False, 
		XA_ATOM, (Atom *) dummy, (int *) dummy, 
		&nActionsReturned, (unsigned long *) dummy,
		(unsigned char **) &actionsReturned);

	nSendActions = nActionsReturned;
	sendActions = malloc(sizeof(Atom) * (nActionsReturned + nActions));
	memcpy(sendActions, actionsReturned, sizeof(Atom) * nActionsReturned);
	
	//Adds any new actions
	for (i = 0; i < nActions && nSendActions < maxActionsAllowed; i++)
	{
		int j, flag = 0;
		for (j = 0; j < nActionsReturned; j++)
		{
			if (actions[i] == actionsReturned[j])
			{
				flag = 1;
				break;
			}
		}
		if (!flag)
			sendActions[nSendActions++] = actions[i];
	}

	if (nSendActions != nActionsReturned)
		XChangeProperty (dpy, win, aAllowedActions,
			 XA_ATOM, 32, PropModeReplace,
			 (unsigned char *) sendActions, nSendActions);

	free(sendActions);
}

/* stolen too */
/*static void
sendClientMessage(Display *d, Window root, Window w, Atom message_type,
                long param0, long param1, long param2,
                long param3, long param4)
{
    XEvent xev;

    xev.xclient.type = ClientMessage;
    xev.xclient.serial = 0;
    xev.xclient.send_event = True;
    xev.xclient.window = w;
    xev.xclient.message_type = message_type;
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = param0;
    xev.xclient.data.l[1] = param1;
    xev.xclient.data.l[2] = param2;
    xev.xclient.data.l[3] = param3;
    xev.xclient.data.l[4] = param4;

    XSendEvent (d, root, False,
                SubstructureRedirectMask | SubstructureNotifyMask,
                &xev);
}*/

static void
sendWmStateMessages(Display *dpy, int screen, Window win, char* states[], int nStates)
{
	int i = 0;
	//Atom msg = XInternAtom (dpy, "_NET_WM_STATE", 0);
	//Window root = XRootWindow(dpy, screen);
	Atom aStates[nStates];

	if (nStates == 0)
		return;

	for (i = 0; i < nStates; i++)
	{
		bwpsPrintf("Setting state: %s\n", states[i]);
		aStates[i] = XInternAtom(dpy, states[i], 0);
		/*sendClientMessage(dpy, root, win, msg,
			1, XInternAtom(dpy, states[i], 0),
			0, 0, 0);*/
	}

	bwpsUnmapWindow(dpy, win);
	XChangeProperty (dpy, win, XInternAtom (dpy, "_NET_WM_STATE", 0),
		 XA_ATOM, 32, PropModeReplace,
		 (unsigned char *) aStates, nStates);
	XMapWindow(dpy, win);

}

static void
sendWmLevelMessages(Display *dpy, Window win, struct levelData levels[], int nLevels)
{
    CARD32 o;
	int i;

	if (nLevels == 0)
		return;
	for (i = 0; i < nLevels; i++)
	{
		bwpsPrintf("Level Change %s (%d%%)\n",
			levels[i].atom_name,
			(unsigned int) (levels[i].percentage * 100));

		o = (unsigned long) (levels[i].percentage * SOLID);
		if (levels[i].percentage == 1)
			o = o - 1; //bug workaround
		XChangeProperty (dpy, win, XInternAtom (dpy, levels[i].atom_name, 0),
					 XA_CARDINAL, 32, PropModeReplace,
					 (unsigned char *) &o, 1);
	}

}


static void
setNoInput(Display *dpy, Window win, int noInput)
{
	Region region;

	if (!noInput)
		return;

	region = XCreateRegion ();
	if (region)
	{
		XShapeCombineRegion (dpy, win, ShapeInput, 0, 0, region, ShapeSet);
		XDestroyRegion (region);
	}
}

static void
setNoFocus(Display *dpy, Window win, int noFocus)
{
    XWMHints xwmh;
    xwmh.flags = InputHint;
    xwmh.input = !noFocus;
	XSetWMHints(dpy, win, &xwmh);
}

static void
moveWindow(Display *dpy, Window win, int x, int y, int force)
{
	//force doesn't do anything, but this may change
	XMoveWindow(dpy, win, x, y);
}

static void
resizeWindow(Display *dpy, Window win, int width, int height, int force)
{
	if (force) //changes the wm hints
	{
		XSizeHints wmsh;
		long flags = 0;
		XGetWMNormalHints(dpy, win, &wmsh, &flags);
		if (flags & PSize)
		{
			wmsh.width = width;
			wmsh.height = height;
		}
		if (flags & PMinSize)
		{
			wmsh.min_width = MIN(width, wmsh.min_width);
			wmsh.min_height = MIN(height, wmsh.min_height);
		}
		if (flags & PMaxSize)
		{
			wmsh.max_width = MAX(width, wmsh.max_width);
			wmsh.max_height = MAX(height, wmsh.max_height);
		}
		wmsh.flags = (flags & (PSize | PMinSize | PMaxSize));
		XSetWMNormalHints(dpy, win, &wmsh);
	}
	XResizeWindow(dpy, win, width, height);
}

int
main (int argc, char **argv)
{
	Display *dpy;
	unsigned int screen;
    Window win = 0; //The window we'll be setting properties for
	char *states[MAX_ARGS]; //wmState Messages
	struct levelData levels[MAX_ARGS];
	int nStates = 0, nLevels = 0; //Number of messages we'll be sending
	int noInput, noFocus, noBorder, fullscreen;
	int x, y;
	unsigned int width, height;
	int i;
	int movingWindow, resizingWindow;
	char *displayName = NULL;

	noInput = noFocus = noBorder = fullscreen = 0;
	movingWindow = resizingWindow = 0;
	width = height = x = y = 0;

	if (argc == 1)
		usage();

	for (i = 1; i < argc && i < MAX_ARGS; i++)
	{
		int iWmStateArg, iLevelArg;
		int flag = 0;

		//Loop through all known state message
		//arguments and if add to state args array if necessary
		for (iWmStateArg = 0; iWmStateArg < nWmStateArgs; iWmStateArg++)
		{
			if (!strcmp(argv[i] + 1, wmStateArgs[iWmStateArg][0]))
			{
				states[nStates] = wmStateArgs[iWmStateArg][1];
				nStates++;
				flag = 1;
				continue;
			}
		}
		if (flag) continue;

		for (iLevelArg = 0; iLevelArg < nWmLevelArgs; iLevelArg++)
		{
			if (!strcmp(argv[i] + 1, wmLevelArgs[iLevelArg][0]))
			{
				if (++i >= argc)
					usage();

				levels[nLevels].atom_name = wmLevelArgs[iLevelArg][1];
				levels[nLevels].percentage = atof(argv[i]);
				nLevels++;
				flag = 1;
				continue;
			}
		}
		if (flag) continue;

		if (!strcmp(argv[i], "-fs")) {
			//Fullscreen is handled a little differently
			fullscreen = 1;
			states[nStates] = "_NET_WM_STATE_FULLSCREEN";
			nStates++;
		} else if (!strcmp(argv[i], "-ni")) {
			noInput = 1;
		} else if (!strcmp(argv[i], "-nf")) {
			noFocus = 1;
		} else if (!strcmp(argv[i], "-nb")) {
			noBorder = 1;
		} else if (!strcmp(argv[i], "-g")) {
			int setGeometry;
			if (++i >= argc)
				usage();
			setGeometry = XParseGeometry(argv[i], &x, &y, &width, &height);
			if (!setGeometry)
				fatalError("Invalid Geometry: %s\n", argv[i]);
			movingWindow = setGeometry & (XValue | YValue);
			resizingWindow = setGeometry & (WidthValue | HeightValue);
		} else if (!strcmp(argv[i], "-q")) {
			quiet = 1;
		} else if (!strcmp(argv[i], "-d")) {
			if (++i >= argc)
				usage();
			displayName = argv[0];
		} else if (!strcmp(argv[i], "-id")) {
			//Yes, this was stolen from dsimple.c, i'm a bad man
			if (++i >= argc)
				usage();
			win = 0;
			sscanf(argv[i], "0x%lx", &win);
			if (!win)
				sscanf(argv[i], "%lu", &win);
			if (!win)
				fatalError("Invalid window id format: %s\n", argv[i]);
			continue;
		} else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
			usage();
		} else {
			usage();
		}
	}

	dpy = XOpenDisplay(displayName);
	screen = XDefaultScreen(dpy);

	if (!win)
		win = selectWindow(dpy, screen);

	bwpsPrintf("Setting properties for window: 0x%lx\n", win);

	if (nStates && !movingWindow)
	{
		char dummy[32];
		movingWindow = 1;
		XGetGeometry(dpy, win, (Window *) dummy,
			&x, &y,
			(unsigned int *) dummy, (unsigned int *) dummy,
			(unsigned int *) dummy, (unsigned int *) dummy);
	}

	if (fullscreen)
		initializeFullscreen(dpy, win);
	sendWmStateMessages(dpy, screen, win, states, nStates);
	sendWmLevelMessages(dpy, win, levels, nLevels);

	if (noFocus)
		setNoFocus(dpy, win, noFocus);
	if (noInput)
		setNoInput(dpy, win, noInput);
	if (noBorder)
		setNoBorder(dpy, win, noBorder);

	XSync(dpy, False); //is this needed here..?

	if (movingWindow)
		moveWindow(dpy, win, x, y, noBorder);
	if (resizingWindow)
		resizeWindow(dpy, win, width, height, 1);
	XSync(dpy, False);

	XCloseDisplay(dpy);

	return 0;
}
